﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ResxCrunch.Utils;
using log4net;
using System.Data;
using System.IO;
using System.Resources;
using System.Collections;
using System.Xml;
using System.Xml.Schema;

namespace ResxCrunch.Business.Managers
{
    /// <summary>
    /// Android resource file handler
    /// </summary>
    public class AndroidManager : Singleton<AndroidManager>
    {
        #region Constants
        /// <summary>
        /// Logger declaration
        /// </summary>
        private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// The start part of the resource directory structure
        /// Ex. values-de
        /// </summary>
        private const string AndroidResourceDirectoryPrefix = "values";

        /// <summary>
        /// The separator character separating the prefix from the iso country code
        /// Ex. values-es
        /// </summary>
        private const string AndroidResourceDirectorySeparator = "-";

        /// <summary>
        /// The name of the file containing the actual strings
        /// These files must be placed in the culture directory like: values-es/strings.xml
        /// </summary>
        private const string AndroidStringsFilename = "strings.xml";

        const string DefaultTableColumnKey = "Key";
        const string DefaultTableColumnValue = "Value";
        #endregion Constants

        #region Public methods
        /// <summary>
        /// Loads the different cultures that belong to the files in the selected dir
        /// </summary>
        /// <param name="path">Directory that can contain files with different cultures</param>
        /// <param name="recursive">Should subdirs be searched too?</param>
        /// <returns>List of different cultures, including default culture (string.Empty) too</returns>
        public List<string> GetDifferentCulturesFromDirectories(string path, bool recursive)
        {
            return this.GetDifferentCulturesFromDirectories(path, string.Format("{0}{1}*", AndroidResourceDirectoryPrefix, AndroidResourceDirectorySeparator), AndroidResourceDirectoryPrefix, recursive);
        }

        /// <summary>
        /// Loads the different cultures that belong to the files in the selected dir
        /// </summary>
        /// <param name="path">Directory that can contain files with different cultures</param>
        /// <param name="mask">The mask of the loaded files</param>
        /// <param name="defaultMask">This value is also added to the results</param>
        /// <param name="recursive">Should subdirs be searched too?</param>
        /// <returns>
        /// List of different cultures, including default culture (string.Empty) too
        /// </returns>
        public List<string> GetDifferentCulturesFromDirectories(string basePath, string mask, string defaultMask, bool recursive)
        {
            List<string> directoryList = FileMngr.Instance.GetDirectoryList(mask, basePath, recursive);

            if (directoryList == null)
            {
                log.WarnFormat("Error loading directory list from '{0}', returning null as resource data.", basePath);
                return null;
            }

            List<string> cultures = new List<string>();

            string culture;

            // Get the different cultures
            foreach (string directoryName in directoryList)
            {
                culture = this.ExtractCultureFromAndroidDirectoryName(directoryName);

                if (!cultures.Contains(culture))
                {
                    cultures.Add(culture);
                }
            }

            if (Directory.Exists(Path.Combine(basePath, defaultMask)))
            {
                cultures.Add(string.Empty);
            }

            return cultures;
        }

        /// <summary>
        /// Extracts the culture from the name of the resx file
        /// </summary>
        /// <param name="fullPath">The path and name of the file that needs to be processed</param>
        /// <returns>Culture extracted from the filename</returns>
        public string ExtractCultureFromAndroidDirectoryName(string fullPath)
        {
            DirectoryInfo di = new DirectoryInfo(fullPath);
            string directoryName = di.Name;
            //.Substring(0, di.Name.LastIndexOf(AndroidResourceDirectorySeparator));

            string[] directoryNameParts = directoryName.Split(new string[] { AndroidResourceDirectorySeparator }, StringSplitOptions.None);

            if (directoryNameParts.Length == 2)
            {
                // values-es                                       
                return directoryNameParts[1];
            }

            return string.Empty;
        }

        /// <summary>
        /// Loads the content of resx files into a datatable
        /// </summary>
        /// <param name="path">Directory where the resx files are located</param>
        /// <param name="recursive">Subdirs should be searched too</param>
        /// <returns>Datatable filled with data from the files</returns>
        public DataTable LoadAllAndroidTextResourceXml(string basePath, bool recursive)
        {
            List<string> cultures = this.GetDifferentCulturesFromDirectories(basePath, true);
            List<string> directoryList = FileMngr.Instance.GetDirectoryList(string.Format("{0}{1}*", AndroidResourceDirectoryPrefix, AndroidResourceDirectorySeparator), basePath, recursive);
            directoryList.AddRange(FileMngr.Instance.GetDirectoryList(AndroidResourceDirectoryPrefix, basePath, recursive));

            if (directoryList == null)
            {
                log.WarnFormat("Error loading directory list from '{0}', returning null as resource data.", basePath);
                return null;
            }

            DataTable table = this.CreateTableWithStandardStructure(cultures);

            int counter = 1;
            foreach (string directory in directoryList)
            {
                if (this.IsDefaultCulture(directory))
                {
                    // directory c:\zxc\asdf\values 
                    // basedir = c:\zxc\
                    DataTable defaultValuesTable = this.LoadAndroidTextResourceXml(directory, basePath, ref counter, cultures);

                    foreach (DataRow dr in defaultValuesTable.Rows)
                    {
                        table.ImportRow(dr);
                    }
                }
            }

            return table;
        }

        /// <summary>
        /// Decides if resx file is culture specific (name contains culture like file.de.resx)
        /// </summary>
        /// <param name="path">The full path and filename to the resx file</param>
        /// <returns>True if culture specific</returns>
        public bool IsDefaultCulture(string path)
        {
            if (this.ExtractCultureFromAndroidDirectoryName(path).Length == 0)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Loads the data of a resx file into a datatable
        /// </summary>
        /// <param name="directPath">The direct path that is only missing the strings.xml filename.</param>
        /// <param name="basePath">The base path to the resx files</param>
        /// <param name="counter">The counter.</param>
        /// <param name="cultures">List of different cultures</param>
        /// <returns>
        /// Datatable containing data loaded from the resx file
        /// </returns>
        public DataTable LoadAndroidTextResourceXml(string directPath, string basePath, ref int counter, List<string> cultures)
        {
            DataTable resTable = this.CreateTableWithStandardStructure(cultures);

            // <Culture, Filename>
            Dictionary<string, XmlDocument> readerList = new Dictionary<string, XmlDocument>();

            #region Store all xml paths to different cultures
            foreach (string c in cultures)
            {
                XmlDocument xmlDocument;

                if (c.Length > 0)
                {
                    //string cultureFileBasePath = string.Concat(AndroidResourceDirectoryPrefix, AndroidResourceDirectorySeparator, c);
                    //cultureFileBasePath = Path.Combine(cultureFileBasePath, AndroidStringsFilename);
                    string cultureFilePathWithFileName = Path.Combine(string.Concat(directPath, AndroidResourceDirectorySeparator, c), AndroidStringsFilename);
                    //Path.Combine(basePath, cultureFileBasePath);

                    if (File.Exists(cultureFilePathWithFileName))
                    {
                        xmlDocument = new XmlDocument();

                        using (StreamReader reader = new StreamReader(cultureFilePathWithFileName))
                        {
                            xmlDocument.LoadXml(reader.ReadToEnd());
                        }

                        readerList.Add(c, xmlDocument);
                    }
                }
            }
            #endregion Store all xml paths to different cultures

            string fullFilePath = Path.Combine(directPath, AndroidStringsFilename);
            FileInfo fi = new FileInfo(fullFilePath);
            //string fileName = fi.Name;
            string relativePath = FileMngr.Instance.RelativeDir(basePath, fi.DirectoryName);

            try
            {
                #region Read data
                DataTable defaultValuesTable = this.LoadAndroidTextResourceKeyValuePairs(fullFilePath);

                // Load all the velues having the default values as base
                foreach (DataRow defaultRow in defaultValuesTable.Rows)
                {
                    string key = defaultRow[DefaultTableColumnKey].ToString();
                    string value = defaultRow[DefaultTableColumnValue].ToString();

                    DataRow dr = resTable.NewRow();

                    dr[Enums.DataColumn.Nr] = counter;
                    dr[Enums.DataColumn.Directory] = relativePath;
                    dr[Enums.DataColumn.File] = AndroidStringsFilename;
                    dr[Enums.DataColumn.Key] = key;

                    value = value.Replace("\r", "\\r");
                    value = value.Replace("\n", "\\n");
                    dr[Enums.DataColumn.DefaultValue] = value;

                    // Get the values from other cultures
                    foreach (string c in cultures)
                    {
                        if (c.Length > 0)
                        {
                            if (readerList.ContainsKey(c))
                            {
                                string obtainedValue = this.GetValueFromAndroidResource(readerList[c], key);

                                dr[c] = obtainedValue;
                            }
                        }
                    }

                    resTable.Rows.Add(dr);

                    counter++;
                }
                #endregion Read data
            }
            catch (Exception ex)
            {
                log.Error(string.Format("A problem occured reading '{0}' file's other cultures", directPath), ex);
            }

            return resTable;
        }

        /// <summary>
        /// Gets the value from RESX.
        /// </summary>
        /// <param name="resXResourceReader">The res X resource reader.</param>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        private string GetValueFromResx(ResXResourceReader resXResourceReader, string key)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Saves data from a strict structure table to one or more android files in a specified directory
        /// </summary>
        /// <param name="data">Table containing data in a specific format
        /// File, Key, Default value, de, es</param>
        /// <param name="savePath">The dir where the files will be saved relative to</param>
        /// <returns>True on success, else false</returns>
        public bool SaveAndroidGroup(DataTable data, string savePath)
        {
            if (data == null)
            {
                log.Error("Data is null");
                return false;
            }

            if (!data.Columns.Contains(Enums.DataColumn.Directory) ||
                !data.Columns.Contains(Enums.DataColumn.File) ||
                !data.Columns.Contains(Enums.DataColumn.Key) ||
                !data.Columns.Contains(Enums.DataColumn.DefaultValue))
            {
                log.Error("Data does not contain necessary columns");
                return false;
            }

            try
            {
                List<string> cultures = DataMngr.Instance.ExtractCulturesFromResxCrunchDataSource(data);

                // Resx writers for other cultures
                Dictionary<string, XmlTextWriter> cultureSpecWriterList = null;

                // Sort the rows so records that belong to the same resource can be processed together
                DataView dvSortedEntries = new DataView(data, string.Empty, string.Format("{0}, {1}", Enums.DataColumn.Directory, Enums.DataColumn.File), DataViewRowState.CurrentRows);
                DataTable sortedEntriesTable = dvSortedEntries.ToTable();

                string actualPath = string.Empty;
                string fullPathAndFilename = string.Empty;
                string path = string.Empty;
                XmlTextWriter tw = null;

                foreach (DataRow dr in sortedEntriesTable.Rows)
                {
                    if (dr[Enums.DataColumn.File].ToString().Length > 0)
                    {
                        string newPath = dr[Enums.DataColumn.Directory].ToString();

                        if (!newPath.Equals(actualPath, StringComparison.InvariantCultureIgnoreCase))
                        {
                            // If the writer is open close it, so a new value can be written in it later
                            if (tw != null)
                            {
                                //                                tw.WriteLine("</resources>");
                                tw.WriteEndElement();
                                tw.Close();
                            }

                            actualPath = newPath;

                            // Complete the relative path with the savepath
                            fullPathAndFilename = Path.Combine(Path.Combine(savePath, actualPath), AndroidStringsFilename);
                            path = Path.Combine(savePath, dr[Enums.DataColumn.Directory].ToString());

                            // Create dir for the file if needed
                            if (!Directory.Exists(path))
                            {
                                Directory.CreateDirectory(path);
                            }

                            //tw = new StreamWriter(fullPathAndFilename, false, Encoding.UTF8);
                            tw = new XmlTextWriter(fullPathAndFilename, Encoding.UTF8);
                            tw.Formatting = Formatting.Indented;
                            tw.WriteStartDocument();
                            //tw.WriteLine("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
                            //tw.WriteLine("<resources>");
                            tw.WriteStartElement("resources");

                            // Create writers for other resources
                            if (cultureSpecWriterList != null)
                            {
                                foreach (string c in cultures)
                                {
                                    //cultureSpecWriterList[c].WriteLine("</resources>");
                                    cultureSpecWriterList[c].WriteEndElement();
                                    cultureSpecWriterList[c].Close();
                                }
                            }

                            //cultureSpecWriterList = new Dictionary<string, StreamWriter>();
                            cultureSpecWriterList = new Dictionary<string, XmlTextWriter>();

                            foreach (string c in cultures)
                            {
                                //string cultureFileName = string.Concat(FileMngr.Instance.ExtractNameFromFilename(FileMngr.Instance.ExtractFileName(fullPathAndFilename)), ".", c, FileMngr.Instance.ExtractFileExtension(fullPathAndFilename));
                                // Create the path to the culture specific file
                                string culturePath = string.Empty;
                                string fullCulturePathAndFilename = string.Empty;

                                if (c.Length > 0)
                                {
                                    culturePath = string.Concat(FileMngr.Instance.ExtractFileDir(fullPathAndFilename), AndroidResourceDirectorySeparator, c);
                                    fullCulturePathAndFilename = Path.Combine(culturePath, AndroidStringsFilename);
                                }
                                else
                                {
                                    culturePath = FileMngr.Instance.ExtractFileDir(fullPathAndFilename);
                                    fullCulturePathAndFilename = Path.Combine(culturePath, AndroidStringsFilename);
                                }

                                if (!Directory.Exists(culturePath))
                                {
                                    Directory.CreateDirectory(culturePath);
                                }

                                //StreamWriter cultureWriter = new StreamWriter(fullCulturePathAndFilename, false, Encoding.Unicode);
                                //cultureWriter.WriteLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
                                //cultureWriter.WriteLine("<resources>");

                                XmlTextWriter cultureWriter = new XmlTextWriter(fullCulturePathAndFilename, Encoding.Unicode);
                                cultureWriter.WriteStartElement("resources");

                                // Add the created xml writer to the writer list
                                cultureSpecWriterList.Add(c, cultureWriter);
                            }
                        }

                        // Write default value
                        //tw.Write(string.Format("<string name=\"{0}\">{1}</string>", dr[Enums.DataColumn.Key], dr[Enums.DataColumn.DefaultValue]));
                        //dr[Enums.DataColumn.Key].ToString(), dr[Enums.DataColumn.DefaultValue].ToString());
                        tw.WriteStartElement("string");
                        tw.WriteAttributeString("name", dr[Enums.DataColumn.Key].ToString());
                        //dr[Enums.DataColumn.Key].ToString()
                        tw.WriteString(dr[Enums.DataColumn.DefaultValue].ToString());
                        tw.WriteFullEndElement();

                        foreach (string c in cultures)
                        {
                            //cultureSpecWriterList[c].Write(string.Format("<string name=\"{0}\">{1}</string>", dr[Enums.DataColumn.Key], dr[Enums.DataColumn.DefaultValue]));

                            cultureSpecWriterList[c].WriteStartElement("string");
                            cultureSpecWriterList[c].WriteAttributeString("name", dr[Enums.DataColumn.Key].ToString());
                            cultureSpecWriterList[c].WriteString(dr[Enums.DataColumn.DefaultValue].ToString());
                            cultureSpecWriterList[c].WriteFullEndElement();
                        }
                    }
                }

                if (tw != null)
                {
                    //tw.WriteLine("</resources>");
                    tw.WriteEndElement();
                    tw.Close();
                }

                if (cultureSpecWriterList != null)
                {
                    foreach (string c in cultures)
                    {
                        //cultureSpecWriterList[c].WriteLine("</resources>");
                        cultureSpecWriterList[c].WriteEndElement();
                        cultureSpecWriterList[c].Close();
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error("Error while saving resx group.", ex);
                return false;
            }

            return true;
        }
        #endregion Public methods

        #region Private methods
        /// <summary>
        /// Creates the table with standard structure, containing all the necessary columns the program needs to work with it
        /// Additionally adds the cultures columns (the culture "", or default culture, is not added)
        /// </summary>
        /// <returns>An empty datatable with the correct structure</returns>
        private DataTable CreateTableWithStandardStructure(List<string> cultures)
        {
            DataTable baseTable = new DataTable("Data");
            baseTable.Columns.Add(Enums.DataColumn.Nr, typeof(int));
            baseTable.Columns.Add(Enums.DataColumn.Directory);
            baseTable.Columns.Add(Enums.DataColumn.File);
            baseTable.Columns.Add(Enums.DataColumn.Key);
            baseTable.Columns.Add(Enums.DataColumn.DefaultValue);

            foreach (string c in cultures)
            {
                if (c.Length > 0)
                {
                    baseTable.Columns.Add(c);
                }
            }

            return baseTable.Clone();
        }

        /// <summary>
        /// Loads the android resource key value pairs from an adroid resource xml.
        /// </summary>
        /// <param name="filename">The filename.</param>
        /// <returns>A datatable with 2 columns (key, value) containing values</returns>
        public DataTable LoadAndroidTextResourceKeyValuePairs(string filename)
        {
            DataTable resultTable = new DataTable("Data");
            resultTable.Columns.Add(DefaultTableColumnKey, typeof(string));
            resultTable.Columns.Add(DefaultTableColumnValue, typeof(string));

            try
            {
                XmlDocument xml = new XmlDocument();

                using (StreamReader reader = new StreamReader(filename))
                {
                    xml.LoadXml(reader.ReadToEnd());
                }

                XmlNode root = xml.DocumentElement;

                DataRow newRow;
                foreach (XmlNode node in root.ChildNodes)
                {
                    if (node.NodeType == XmlNodeType.Element && node.Attributes.Count > 0)
                    {
                        newRow = resultTable.NewRow();
                        newRow[DefaultTableColumnKey] = node.Attributes[0].Value;
                        newRow[DefaultTableColumnValue] = node.InnerText;

                        resultTable.Rows.Add(newRow);
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error(string.Format("Error loading data from XML file '{0}'", filename), ex);
            }

            return resultTable;
        }

        /// <summary>
        /// Gets the value from android resource based on a key.
        /// The key is the "name" attribute
        /// </summary>
        /// <param name="document">The document.</param>
        /// <param name="key">The key.</param>
        /// <returns>The value of the node wich has the name attribute that equals the key</returns>
        private string GetValueFromAndroidResource(XmlDocument document, string key)
        {
            try
            {
                XmlNode resultNode = document.SelectSingleNode(string.Format("//string[@name='{0}']", key));
                if (resultNode != null)
                {
                    return resultNode.InnerText;
                }
            }
            catch (Exception ex)
            {
                log.Error(ex);
            }

            return string.Empty;
        }
        #endregion Private methods
    }
}

